package high.concurrent.server.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import high.concurrent.config.CloseOrderMQConfig;
import high.concurrent.constant.CacheKey;
import high.concurrent.constant.TimeConstant;
import high.concurrent.enums.*;
import high.concurrent.exception.BizException;
import high.concurrent.feign.AddressFeignService;
import high.concurrent.feign.CouponFeignService;
import high.concurrent.feign.ProductFeignService;
import high.concurrent.interceptor.UserInterceptor;
import high.concurrent.mapper.ProductOrderMapper;
import high.concurrent.model.LoginUser;
import high.concurrent.model.OrderMessage;
import high.concurrent.model.ProductOrderModel;
import high.concurrent.pay.factory.PayFactory;
import high.concurrent.pay.template.PayCallbackAbstractTemplate;
import high.concurrent.pay.template.paycallback.PayCallbackChildImpl;
import high.concurrent.request.CreateOrderRequest;
import high.concurrent.request.LockCouponRecordRequest;
import high.concurrent.request.LockProductRequest;
import high.concurrent.request.ProductItemRequest;
import high.concurrent.server.ProductOrderService;
import high.concurrent.utils.CommonUtil;
import high.concurrent.utils.JsonData;
import high.concurrent.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class ProductOrderServiceImpl implements ProductOrderService {

    @Autowired(required = false)
    private ProductOrderMapper productOrderMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired(required = false)
    private CouponFeignService couponFeignService;
    @Autowired(required = false)
    private ProductFeignService productFeignService;
    @Autowired(required = false)
    private AddressFeignService addressFeignService;
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private PayFactory payFactory;

    /**
     * 根据订单号查询订单信息
     * @param outTradeNo  订单号
     * @return
     */
    @Override
    public ProductOrderVO selectOrderByOutTradeNo(String outTradeNo) {

        LoginUser loginUser = UserInterceptor.threadLocal.get();

        // 加上用户id作为条件，防止越权攻击。
        ProductOrderModel productOrderModel = productOrderMapper.selectOne(new QueryWrapper<ProductOrderModel>()
                .eq("out_trade_no", outTradeNo));
//                .eq("user_id", loginUser.getId()));

        ProductOrderVO productOrderVO = new ProductOrderVO();
        BeanUtils.copyProperties(productOrderModel, productOrderVO);
        return productOrderVO;
    }

    /**
     * 获取下单防重令牌token
     * @param accountId  用户id
     * @return
     */
    @Override
    public JsonData getOrderRepayToken(Long accountId) {

        // 拼接redisKey
        String redisKey = String.format(CacheKey.ORDER_REPAY_TOKEN_KEY, accountId);

        // 生成防重令牌token，并保存到redis,过期时间于订单过期时间一致
        String orderPayToken = CommonUtil.getStringNumRandom(32);
        redisTemplate.opsForValue().set(redisKey, orderPayToken);

        return JsonData.buildSuccess(orderPayToken);
    }

    /**
     * 提交订单
     * @param createOrderRequest
     * @param request
     * @return
     */
    @Override
    public JsonData confirmOrder(CreateOrderRequest createOrderRequest, HttpServletRequest request) {

        // 获取用户信息
        LoginUser loginUser = UserInterceptor.threadLocal.get();

        // 校验令牌是否为空
        if(StringUtils.isBlank(createOrderRequest.getToken())){
            log.info("订单令牌缺少: {}", createOrderRequest);
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_NOT_EXIST);
        }

        // redis + lua原子操作： 校验令牌，并删除令牌
        String orderPayTokenKey = String.format(CacheKey.ORDER_REPAY_TOKEN_KEY, createOrderRequest.getUserId());
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        // lua如果返回0，表示失败，即可能不存在令牌，或者发生异常
        Long luaResult = (Long) redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(orderPayTokenKey), createOrderRequest.getToken());
        // 令牌不存在，则返回异常，防止2次提交，第二次会拿不到令牌，因为第一次完成后就会删除掉
        if(luaResult == 0L){
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
        }

        // 创建订单号
        String outTradeNo = CommonUtil.getRandomCode(32);

        // 根据地址ID查询地址详情
        AddressVo addressVO = this.selectAddressDetail(createOrderRequest.getUserId(), createOrderRequest.getAddressId());
        log.info("收获地址: {}", addressVO.toString());

        // 校验购物车商品项的最新价格, 并返回最新的购物商品信息
        List<Long> productIdList = createOrderRequest.getProductIdList();
        List<OrderItemVO> cartItemLatestPrice = this.getCartItemLatestPrice(productIdList);
        log.info("最新购买的商品信息: {}", cartItemLatestPrice);

        // 优惠券验证是否符合要求（验证失败，在子方法直接抛异常了）
        CouponRecordVo couponRecordVo = this.checkCouponRecord(createOrderRequest);

        // 商品验价（验证失败，在子方法直接抛异常了）
        this.checkProductPrice(createOrderRequest, cartItemLatestPrice, couponRecordVo);

        if(createOrderRequest.getCouponRecordId() != null) {
            // 锁定优惠券
            this.lockCouponRecord(createOrderRequest, outTradeNo);
        }

        // 锁定商品库存
        this.lockProductRecord(cartItemLatestPrice, outTradeNo);

        // 创建订单
        this.createOrder(loginUser, outTradeNo, addressVO, createOrderRequest);

        // 发送延迟消息，用于自动关单
        OrderMessage orderMessage = new OrderMessage();
        orderMessage.setOutTradeNo(outTradeNo);
        rabbitTemplate.convertAndSend(CloseOrderMQConfig.EXCHANGE_NAME, CloseOrderMQConfig.DELAY_ROUTING_KEY, orderMessage);

        // 调起支付接口的参数准备
        PayInfoVO payInfoVO = new PayInfoVO(outTradeNo, null, createOrderRequest.getPayAmount(), null, createOrderRequest.getPayType(),
                "测试订单", "第三方支付", TimeConstant.ORDER_PAY_TIMEOUT_MILLS, loginUser.getId());
        String quCode = payFactory.createOrder(payInfoVO);

        // redis保存二维码，并定时30分钟(和订单过期时间一致),给二次查看用。 key = outTradeNo + userId, 防止水平越权攻击
        log.info("创建支付订单成功: payInfoVO= " +  payInfoVO);
        redisTemplate.opsForValue().set(outTradeNo + createOrderRequest.getUserId(), 1, 30, TimeUnit.MINUTES);
        return JsonData.buildSuccess(quCode);
    }

    /**
     * 定时关单
     * 1. 判断该订单是否存在，如果不存在直接确认消息
     * 2. 订单存在，是PAY状态，直接确认消息
     * 3. 订单存在，不是PAY状态，查询第三方接口
     * 4. 第三方接口为SUCCESS，则更改订单状态为PAY
     * 5. 第三方接口不是SUCCESS，则更改订单状态为CANCEL
     * @param orderMessage
     * @return
     */
    @Override
    public boolean checkProductTask(OrderMessage orderMessage) {

        // 查询订单
        ProductOrderModel productOrderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderModel>()
                .eq("out_trade_no", orderMessage.getOutTradeNo()));

        // 判断该订单是否存在，如果不存在直接确认消息
        if(productOrderDO == null){
            log.info("订单不存在, {}", orderMessage);
            return true;
        }

        // 订单存在，是PAY状态，直接确认消息
        if(OrderStateEnum.PAY.name().equalsIgnoreCase(productOrderDO.getState())){
            log.info("订单支付成功, {}", productOrderDO);
            return true;
        }

        // TODO 订单存在，不是PAY状态，查询第三方接口(微信/支付宝)-参数准备
        String rtnState = "SUCCESS";

        // 第三方接口为SUCCESS，则更改订单状态为PAY
        Map<String, Object> mapParam = new HashMap<>();
        mapParam.put("outTradeNo", orderMessage.getOutTradeNo());
        mapParam.put("oldState", OrderStateEnum.NEW.name());

        if("SUCCESS".equalsIgnoreCase(rtnState)){
            log.info("第三方支付成功,更改状态为PAY: {}", productOrderDO);
            productOrderMapper.update(null, new UpdateWrapper<ProductOrderModel>()
                    .eq("out_trade_no", orderMessage.getOutTradeNo())
                    .eq("state", OrderStateEnum.NEW.name()).set("state", OrderStateEnum.PAY.name())
            );
        }else{
            log.info("第三方支付成功,更l改状态为CANCEL: {}", productOrderDO);
            productOrderMapper.update(null, new UpdateWrapper<ProductOrderModel>()
                    .eq("out_trade_no", orderMessage.getOutTradeNo())
                    .eq("state", OrderStateEnum.NEW.name()).set("state", OrderStateEnum.CANCEL.name())
            );
        }

        return true;
    }

    /**
     * 根据商家id查询订购单集合
     * @param businessId  商家id
     * @param page  当前页数
     * @param pageSize  每页数据量
     * @return
     */
    @Override
    public List<ProductOrderVO> selectOrderByBusinessId(Long businessId, Integer page, Integer pageSize) {

        SearchRequest request = new SearchRequest("product_order");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 根据name字段，获取值为参数的数据
        searchSourceBuilder.query(QueryBuilders.matchQuery("businessId", businessId));
        // 从第0条开始，相当于分页的第一页第一条数据
        searchSourceBuilder.from((page - 1) * pageSize);
        // 取5条数据，相当于每页条数
        searchSourceBuilder.size(pageSize);
        request.source(searchSourceBuilder);
        SearchResponse response = null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 取出符合条件的数据，hit = 碰撞，即查出的数据
        SearchHit[] hits = response.getHits().getHits();
        List<ProductOrderVO> ProductOrderVOList = new ArrayList<>();

        for(SearchHit searchHit : hits){
            ProductOrderVO productOrderVO = JSONObject.parseObject(searchHit.getSourceAsString(), ProductOrderVO.class);
            ProductOrderVOList.add(productOrderVO);
        }
        return ProductOrderVOList;
    }

    /**
     * 根据用户id查询订购单集合
     * @param page  当前页数
     * @param pageSize  每页数据量
     * @return
     */
    @Override
    public List<ProductOrderVO> selectOrderByUserId(Integer page, Integer pageSize) {
        LoginUser loginUser = UserInterceptor.threadLocal.get();
        SearchRequest request = new SearchRequest("product_order");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 根据name字段，获取值为参数的数据
        searchSourceBuilder.query(QueryBuilders.matchQuery("userId", loginUser.getId()));
        // 从第0条开始，相当于分页的第一页第一条数据
        searchSourceBuilder.from((page - 1) * pageSize);
        // 取5条数据，相当于每页条数
        searchSourceBuilder.size(pageSize);
        request.source(searchSourceBuilder);
        SearchResponse response = null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 取出符合条件的数据，hit = 碰撞，即查出的数据
        SearchHit[] hits = response.getHits().getHits();
        List<ProductOrderVO> ProductOrderVOList = new ArrayList<>();

        for(SearchHit searchHit : hits){
            ProductOrderVO productOrderVO = JSONObject.parseObject(searchHit.getSourceAsString(), ProductOrderVO.class);
            ProductOrderVOList.add(productOrderVO);
        }
        return ProductOrderVOList;
    }

    /**
     * 回调逻辑执行
     * 该类使用了模板模式，内部具体步骤：
     * 1. 模板模式会通过 工厂+测策略模式进行回调后操作
     * 2. 模板流程上是(向所有相关服务发送消息，并标记待发送---> 处理本地事务，即更改订单表状态---> 再向相关服务发送消息，并标记已发送)
     * 3. 支付类型(1. 网费支付; 2. 转包支付 3. 订餐支付(还未实现); 4. 零食支付(还未实现))
     * 4. 日后追加支付该如何处理？
     *       1. template模板不需要动
     *       2. 去factory文件夹下追加一种类型的实现类(注意是【实现类】不是工厂，也不是接口)
     *       3. 去PayCallbackFactory工厂下，每个方法加一个判断，判断是新类型的就调用新类型的实现
     *       4. strategypattern策略不需要东动
     *       5. 业务不需要动
     *       6. 综上所述，需要动的仅仅是工厂
     * 5. 如何区分各种不同的支付该回调哪些服务？
     *      首先：内部有工厂+策略模式，
     *      其次：策略模式通过【payType】参数进行区分是哪种支付类型
     *      然后：去调用对应支付类型工厂(该支付类型指的是，网费， 转包，订餐或零食。并非微信，支付宝，京东)
     * 6. 日后再加支付类型仍可适用，因为所有的支付回调流程都是一样的，只是改的表不同而已
     * 6.
     * @param productOrderVO
     */
    @Override
    public void returnRrlPath(ProductOrderVO productOrderVO) {

        // 通过模板模式，进行回调处理
        PayInfoVO payInfoVO = new PayInfoVO();
        payInfoVO.setAccountNo(productOrderVO.getUserId());
        payInfoVO.setOutTradeNo(productOrderVO.getOutTradeNo());
        payInfoVO.setPayFee(productOrderVO.getPayAmount());
        payInfoVO.setPayType(productOrderVO.getPayType());
        PayCallbackAbstractTemplate payCallbackAbstractTemplate = new PayCallbackChildImpl(payInfoVO);
        payCallbackAbstractTemplate.processPayCallback();

    }

    /**
     * 创建订单
     * @param loginUser    用户信息
     * @param outTradeNo   订单号
     * @param addressVO    地址信息createOrder
     */
    private ProductOrderModel createOrder(LoginUser loginUser, String outTradeNo, AddressVo addressVO, CreateOrderRequest createOrderRequest) {

        // 构建订单映射对象
        ProductOrderModel productOrderDO = new ProductOrderModel();
        productOrderDO.setDel(0);
        productOrderDO.setHeadImg(loginUser.getHeadImg());
        productOrderDO.setNickname(loginUser.getName());
        productOrderDO.setOrderType(OrderTypeEnum.DAILY.name());
        productOrderDO.setOutTradeNo(outTradeNo);
        // 订单真实支付价格
        productOrderDO.setPayAmount(createOrderRequest.getPayAmount());
        // 订单总价格(不加优惠券)
        productOrderDO.setTotalAmount(createOrderRequest.getTotalAmount());
        // 类型转换PayTypeEnum
        productOrderDO.setPayType(PayTypeEnum.valueOf(createOrderRequest.getPayType()).name());
        productOrderDO.setReceiverAddress(JSON.toJSONString(addressVO));
        productOrderDO.setState(OrderStateEnum.NEW.name());
        productOrderDO.setUserId(loginUser.getId());
        productOrderDO.setUpdateTime(new Date());
        productOrderDO.setCreateTime(new Date());

        // 添加到订单表
        productOrderMapper.insert(productOrderDO);

        return productOrderDO;

    }


    /**
     * 商品验价(优惠券已经确认可以使用了)
     * 1. 计算所有商品总价，并扣除优惠券的优惠价格(注意：如果优惠价格高于支付价格，那直接令支付价格为0)
     * 2. 和前端传的【实际支付价格比较】
     * @param cartItemLatestPrice   后台计算的最新值
     * @param couponRecordVo   优惠券详情信息
     * @return
     */
    private boolean checkProductPrice(CreateOrderRequest createOrderRequest, List<OrderItemVO> cartItemLatestPrice, CouponRecordVo couponRecordVo) {

        // 无需校验购物项是否为空，在获取最新购物项价格已经校验了
        // 计算所有商品总价，并扣除优惠券的优惠价格(注意：如果优惠价格高于支付价格，那直接令支付价格为0)
        final BigDecimal[] latestTotalPrice = {new BigDecimal(0)};
        BigDecimal latestReallyPrice = new BigDecimal(0);
        cartItemLatestPrice.stream().forEach(orderItemVO ->{
            BigDecimal itemTotalAmount = orderItemVO.getTotalAmount();
            latestTotalPrice[0] = latestTotalPrice[0].add(itemTotalAmount);
        });
        if(couponRecordVo == null){
            // 表示没用优惠券
            latestReallyPrice = latestTotalPrice[0];
        }else if(latestTotalPrice[0].compareTo(couponRecordVo.getPrice()) >= 0){
            latestReallyPrice = latestTotalPrice[0].subtract(couponRecordVo.getPrice());
        }

        //和前端传的【实际支付价格比较】
        if(latestReallyPrice.compareTo(createOrderRequest.getPayAmount()) != 0){
            log.info("验价失败，前端传入价格={}， 后台计算价格 = {}", createOrderRequest.getPayAmount(), latestReallyPrice);
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_PRICE_FAIL);
        }

        return true;
    }


    /**
     * 优惠券验证是否符合要求
     * 1. 校验优惠券id为空或者小于0，则直接返回true表示未使用购物券
     * 2. 获取购物券信息
     * 3. 校验购物券是否符合使用要求(满减价格， 时间范围， 优惠全状态为NEW)
     * @param createOrderRequest   前端传的订单信息
     * @return  优惠券详情信息
     */
    private CouponRecordVo checkCouponRecord(CreateOrderRequest createOrderRequest) {

        // 校验是否使用优惠券
        if(createOrderRequest.getCouponRecordId() == null || createOrderRequest.getCouponRecordId() < 0){
            return null;
        }

        // 获取购物券信息
        JsonData couponRecordDetail = couponFeignService.getCouponRecordDetail(createOrderRequest.getCouponRecordId());
        CouponRecordVo couponRecordVo = new CouponRecordVo();
        // 校验优惠券是否存在
        if(couponRecordDetail.getCode() != 0 || couponRecordDetail.getData() == null){
            log.info("优惠券不存在");
            throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
        }else{
            couponRecordVo = couponRecordDetail.getData(new TypeReference<CouponRecordVo>(){});
        }

        // 校验优惠券状态是否为NEW
        if(!CouponUseStatusEnum.NEW.name().equals(couponRecordVo.getUseState())){
            log.info("优惠券已被使用, msg: {}", couponRecordVo);
            throw new BizException(BizCodeEnum.COUPON_RECORD_USED);
        }

        // 校验优惠券时间范围
        Long nowTime = System.currentTimeMillis();
        Long startTime = couponRecordVo.getStartTime().getTime();
        Long endTime = couponRecordVo.getEndTime().getTime();
        if(nowTime < startTime || nowTime > endTime){
            log.info("优惠券不再适用范围内, 开始时间: {}, 结束时间: {}", couponRecordVo.getStartTime(), couponRecordVo.getEndTime());
            throw new BizException(BizCodeEnum.COUPON_RECORD_OUT_OF_USETIME);
        }

        // 校验优惠券是否符合满减
        if(createOrderRequest.getTotalAmount().compareTo(couponRecordVo.getConditionPrice()) < 0){
            log.info("优惠券不符合满减要求: 满减 = {}, 实际={}", couponRecordVo.getConditionPrice(), createOrderRequest.getTotalAmount());
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_COUPON_FAIL);
        }

        return couponRecordVo;
    }


    /**
     * 锁定优惠券
     * @param createOrderRequest  前端传的信息(couponRecordId)
     * @param outTradeNo  订单号
     */
    private void lockCouponRecord(CreateOrderRequest createOrderRequest, String outTradeNo) {

        List<Long> couponRecordIdList = new ArrayList<>();
        // 校验用户使用了优惠券
        if(createOrderRequest.getCouponRecordId() != null && createOrderRequest.getCouponRecordId() > 0){
            // 需要把couponRecordId添加到集合，如果传的couponRecordId是一个集合，那么直接赋值即可
            couponRecordIdList.add(createOrderRequest.getCouponRecordId());
        }

        // 构建锁定优惠券Request, 并进行优惠券锁定
        LockCouponRecordRequest lockCouponRecordRequest = new LockCouponRecordRequest();
        lockCouponRecordRequest.setCouponRecordIdList(couponRecordIdList);
        lockCouponRecordRequest.setOutTradeNo(outTradeNo);
        JsonData jsonData = couponFeignService.lockCouponRecord(lockCouponRecordRequest);

        // 如果锁定失败，则直接抛出异常
        if(jsonData.getCode() != 0){
            log.info("优惠券锁定失败:" + lockCouponRecordRequest);
            throw new BizException(BizCodeEnum.COUPON_RECORD_LOCK_FAIL);
        }
    }


    /**
     * 锁定商品
     * @param cartItemLatestPrice   选中的最新商品信息
     * @param outTradeNo    订单号
     */
    private void lockProductRecord(List<OrderItemVO> cartItemLatestPrice, String outTradeNo) {

        // 遍历商品信息集合，把每个商品信息，赋值到ProductItemRequest中
        List<ProductItemRequest> listProductItemRequests = new ArrayList<>();
        cartItemLatestPrice.stream().forEach(cartItem -> {
            ProductItemRequest productItemRequest = new ProductItemRequest();
            productItemRequest.setBuyNum(cartItem.getBuyNum());
            productItemRequest.setProductId(cartItem.getProductId());
            listProductItemRequests.add(productItemRequest);
        });

        // 构建商品锁定的LockProductRequest
        LockProductRequest lockProductRequest = new LockProductRequest();
        lockProductRequest.setOutTradeNo(outTradeNo);
        lockProductRequest.setProductItemRequestList(listProductItemRequests);
        JsonData jsonData = productFeignService.lockProduct(lockProductRequest);

        // 如果锁定失败，则直接抛出异常
        if(jsonData.getCode() != 0){
            log.info("商品锁定失败:" + lockProductRequest);
            throw new BizException(BizCodeEnum.PRODUCT_LOCK_FAIL);
        }

    }

    /**
     * 根据地址id查询地址详情
     * @param userId      用户id
     * @param addressId   地址id
     * @return
     */
    private AddressVo selectAddressDetail(Long userId, long addressId) {

        // 查询地址，并转型为AddressVO
        JsonData jsonData = addressFeignService.getAddressById(addressId);

        if(jsonData.getData() == null){
            // 该地址不存在，抛出异常
            log.info("地址查询失败: msg:{}", jsonData);
            throw new BizException(BizCodeEnum.ADDRESS_NO_EXITS);
        }
        AddressVo addressVo = jsonData.getData(new TypeReference<AddressVo>(){});
        return addressVo;
    }

    /**
     * 查询购物车商品项的最新价格，并删除对应购物车商品
     * @param productIdList
     */
    private List<OrderItemVO> getCartItemLatestPrice(List<Long> productIdList) {

        List<OrderItemVO> listOrderItemVOs = new ArrayList<>();
        // 查询购物车选中的商品项的最新价格，并删除对应购物车商品
        JsonData jsonData = productFeignService.getCartItemLatestPrice(productIdList);

        // 校验最新购物车信息无误后，返回最新的选中商品信息
        if(jsonData.getData() != null){
            log.info("最新购物车商品: {}", jsonData.getData());
            listOrderItemVOs = jsonData.getData(new TypeReference<List<OrderItemVO>>(){});
        }else{
            log.info("err: 购物车商品不存在");
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_CART_ITEM_NOT_EXIST);
        }

        return listOrderItemVOs;
    }
}
